Skip to content

Provide further Octane support for Caddyfile - #604

Merged
jaydrogers merged 6 commits into
serversideup:release/webserver-improvements-and-fixesfrom
aSeriousDeveloper:patch-1
Sep 16, 2026
Merged

jaydrogers merged 6 commits into
serversideup:release/webserver-improvements-and-fixesfrom
aSeriousDeveloper:patch-1

Conversation

@aSeriousDeveloper

Copy link
Copy Markdown
Contributor

Laravel Octane

Laravel Octane has its own Caddyfile. It uses various variables that are populated during the startup command.

Typically, this means you need to perform your own overrides to this image's FrankenPHP config to make the two work together. Fortunately, this is pretty simple using the following steps:

  1. Updating your environment to use this: (thanks Discord User coolio85!)
CADDY_PHP_SERVER_OPTIONS: |
  index frankenphp-worker.php
  try_files {path} frankenphp-worker.php
  # Required for the public/storage/ directory...
  resolve_root_symlink
FRANKENPHP_CONFIG: |
  worker {
    file "/var/www/html/public/frankenphp-worker.php"
  }
  1. Updating your docker run command to use octane & this config file:
CMD ["php", "artisan", "octane:frankenphp", "--caddyfile=/etc/frankenphp/Caddyfile"]

The Problem

This all works pretty well, up until you want to enable worker mode or specify the number of workers. This is because we're overriding the Caddyfile config with environment variables. This is acceptable within the file itself, but if we attempt to do the same thing within our FRANKENPHP_CONFIG environment variable, we get the folowing:

INFO  Error: adapting config using caddyfile: Unexpected '{}' at end of line, at /etc/frankenphp/Caddyfile:20.

The Fix

To fix this issue, we need to directly include the environment variables within the file. Therefore, this PR does exactly that. This PR adds 2 environment variables that can be overridden to the frankenphp config:

  • CADDY_SERVER_WORKER_DIRECTIVE
  • CADDY_SERVER_WATCH_DIRECTIVES

If they're empty, they should be safely ignored. However, this'll mean they can be populated separately, either automatically by Octane, or manually by the user.

Additionally, if the worker or num directives are already specified elsewhere, it still works! The watch directive will simply append to the watchlist, while num I believe cascades to the last specified value.

Extra Stuff

The Octane config does specify a few other environment variables:

  • CADDY_SERVER_EXTRA_DIRECTIVES
  • CADDY_SERVER_ADMIN_HOST
  • CADDY_SERVER_ADMIN_PORT
  • etc etc I don't wanna list them all out

But these may be more difficult to add in, either because they've already been specified in this Caddyfile (eg CADDY_SERVER_EXTRA_DIRECTIVES), or because they require overriding / changes other parts of the Caddyfile if we wanted to make them work.

Specify the `CADDY_SERVER_WORKER_DIRECTIVE` and `CADDY_SERVER_WATCH_DIRECTIVES` directives that are populated by Laravel Octane
@jaydrogers

Copy link
Copy Markdown
Member

Thanks a ton for chiming in on this @aSeriousDeveloper! I always appreciate reviewing your PRs.

I am a novice with Laravel Octane, so I am still learning the ropes what's the best user experience. To my understanding, there are two Caddyfiles being presented:

Classic Mode: /etc/frankenphp/Caddyfile (provided by ServerSideUp)

  • This file is highly optimized to run in production with caching, SSL, etc

Worker Mode: Use the official Laravel Caddyfile

  • This is completely controlled by Laravel

My first approach (not saying it's 100% right)

I documented to use the following command on start up:

services:
  php:
    image: serversideup/php:8.4-frankenphp
    ports:
      - "80:8080"
    volumes:
      - .:/var/www/html/
    # Start Octane in worker mode
    command: ["php", "artisan", "octane:start", "--server=frankenphp", "--port=8080"]
    # Set healthcheck to use our native healthcheck script for Octane
    healthcheck:
      test: ["CMD", "healthcheck-octane"]
      start_period: 10s

Note

Notice how I'm NOT specifying a Caddyfile. This will use Laravel's default Caddyfile.

If we suggest to just use Laravel's default Caddyfile

There are pros and cons to this approach:

Pros

  • As Laravel releases new versions of Octane (which may be compatible with only certain versions of Laravel), they can specifically control the Caddyfile that's compatible with that version of Octane
  • When people configure Laravel Octane, we can exactly follow Laravel's official docs

Cons

  • Not all variables from our documentation will be available (specifically the CADDY_* variables), but our PHP_* variables will still work

Next steps

Let me know your thoughts what you think makes sense. I am very interested in having a community discussion on this.

I can easily add extra variables that you propose, but my only hesitation is if Laravel starts changing variables with newer versions of Octane that only are available in Laravel 13+, then this could turn into a lot of maintenance and documentation for people who are running Laravel 12 or lower.

Thoughts?

@aSeriousDeveloper

aSeriousDeveloper commented Nov 12, 2025

Copy link
Copy Markdown
Contributor Author

@jaydrogers Yeah good point on wanting it to be more easily configurable & not have compatibility problems. However, I do still want some of the improved Caddyfile features...

Not entirely sure what the best-fit solution would be in this case, some spitball suggestions though:

Injecting as much as possible using Octanes exposed environment variables

Namely, the CADDY_EXTRA_CONFIG doesn't seem to get overridden within the Octane startup, so you should be able to use that. It's within the global scope of the Caddyfile so you should be able to perform both global & server based configs with it?

Then again, loading a bunch of config via an environment variable (even if you just do it via import) feels wonky.

Advising to change the config to the one provided here

This means they get the specialised config provided, and if we include the extra env variables, then they get the Octane config too. However, it runs into the problem you mentioned of future compatibility, so ehhh.

Providing a secret, third config

Not sure on this one, but either provide the config as an additional one alongside the existing one within the image (feels bloated), or through something like Spin Pro means you can have it there for those who want it. Namely, if it were part of Spin Pro or something like that, you can have it automatically included within the Dockerfile / Docker Compose. It'd effectively be the same config included here but with the added environment variables as much as possible, as well as maybe cut back on what becomes redundant? Such as not needing the SSL mode configs anymore as it'll always be behind a reverse proxy.

@jaydrogers

Copy link
Copy Markdown
Member

I love this discussion! We're definitely on the same page.

Maybe we start here when you said:

However, I do still want some of the improved Caddyfile features...

What features from the Server Side Up Caddyfile do you feel are missing in the default Octane configuration (specifically in the use case of running Octane).

I know we provide a lot of caching and security stuff, but is all our stuff compatible with Octane (or even worker mode in general)? 😅

@aSeriousDeveloper

Copy link
Copy Markdown
Contributor Author

I think it's mostly because I don't wanna lose anything that was previously added, but the big ones that stand out are:

  • Cloudflare Proxying (very useful imo)
  • The healthcheck (although that might have support in Octane already)
  • Asset Caching
  • Filetype rejections & Header removal (although these can be handled by Cloudflare anyway so eh)

Honestly overall though, I think it's mostly just peace of mind, outside of the Cloudflare proxying which I do very much like the concept of. Then again, isn't this also supported by a Caddy Module?

@jaydrogers

Copy link
Copy Markdown
Member

You're right...

We've gotta bring these variables in. I don't want to lose that either 🤣

I am in the middle of a project today, but I will have more thought on this tomorrow. Feel free to add more notes to this PR if you have additional thoughts too.

@kohenkatz

Copy link
Copy Markdown
Contributor

Honestly overall though, I think it's mostly just peace of mind, outside of the Cloudflare proxying which I do very much like the concept of. Then again, isn't this also supported by a Caddy Module?

The big thing for me is also the Cloudflare proxy trust (along with also trusting our Docker network and own AWS EC2 IPv6 range).

Right now I use a custom Caddyfile and a custom Caddy build with Octane.

This uses the Cloudlfare IPs module linked above, along with the combine module. I don't think Cloudflare changes their IP ranges very often anymore, so it's probably fine to keep using a static list instead of the module, but not having to provide my own Caddyfile would be nice.

My thought is that Octane doesn't really need much to be changed in the Caddyfile - just a few lines that reference frankenphp-worker.php. Maybe an import php-app-octane that can be enabled with an environment variable, similar to the existing way of enabling/disabling features like SSL mode?


Here are the relevant snippets of what I have done until now:

Dockerfile

FROM dunglas/frankenphp:1.9.1-builder-php8.4.13-trixie AS builder

COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy

RUN CGO_ENABLED=1 \
    XCADDY_SETCAP=1 \
    XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
    CGO_CFLAGS=$(php-config --includes) \
    CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
    xcaddy build \
        --output /usr/local/bin/frankenphp \
        # Default modules (except vulcain)
        --with github.com/dunglas/frankenphp=./ \
        --with github.com/dunglas/frankenphp/caddy=./caddy/ \
        --with github.com/dunglas/caddy-cbrotli \
        --with github.com/dunglas/mercure/caddy \
        # Our extra Caddy modules here
        --with github.com/fvbommel/caddy-combine-ip-ranges \
        --with github.com/fvbommel/caddy-dns-ip-range \
        --with github.com/WeidiDeng/caddy-cloudflare-ip

FROM dunglas/frankenphp:1.9.1-php8.4.13-trixie AS runner

# [SNIP - app-specific stuff]

COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp

Caddyfile

This is the only part that is added to the original Octane Caddyfile, at the end of the global options section.

	servers {
		# Docker/AWS Local IPv4, Docker IPv6, AWS IPv6, and the rest are Cloudflare (updated 2024-07-31)
		trusted_proxies combine {
			# Docker and AWS local IPv4 and IPv6
			static 172.16.0.0/12 fd00::/8 2600:f18:xxxx:xxxx::/56
			cloudflare {
				interval 12h
				timeout 15s
			}
		}
		trusted_proxies_strict
	}

@aSeriousDeveloper

Copy link
Copy Markdown
Contributor Author

My thought is that Octane doesn't really need much to be changed in the Caddyfile - just a few lines that reference frankenphp-worker.php. Maybe an import php-app-octane that can be enabled with an environment variable, similar to the existing way of enabling/disabling features like SSL mode?

Yeah this is largely true. The core pieces of Octane are essentially just loading the worker. However the additional crux for me comes from the specific variables it also references for building its config.

(Also, another feature I neglected to directly mention, the support it includes for Mercure via the CADDY_SERVER_EXTRA_DIRECTIVES variable).

Having an imported config would probably solve some of the issues, but I was thinking:

Considering this image & the caddyfile are still within beta, what would be solved and what problems would be caused if we more directly emulated Octane's config? Looking at it, probably wouldn't solve much and would be pretty annoying to do, but I thought I'd mention at least.

But looking again, specific areas that clash are:

There's other bits that are different between the files but they can be more easily reconciled through just setting environment variables.

Considering all that, if we did wanna make these two work together, it probably would require either rejigging our own Caddyfile heavily to have it mesh properly (including renaming our environment variables to clash less), or to have a dedicated Caddyfile for Octane?

Alternatively, maybe it's possible to place a PR into Laravel to change their Caddyfile variables to be more Octane specific? They use generic Caddy names but unless these variables are merged somehow that just causes whatever you've set to get overridden.

@jaydrogers

Copy link
Copy Markdown
Member

Man, I am really torn on this. I think for the v4.0 release (which I am hoping to move to stable this week 🤞), we'll just stick with our documented process on using the official Laravel Octane Caddyfile:

services:
  php:
    image: serversideup/php:8.4-frankenphp
    ports:
      - "80:8080"
    volumes:
      - .:/var/www/html/
    # Start Octane in worker mode
    command: ["php", "artisan", "octane:start", "--server=frankenphp", "--port=8080"]
    # Set healthcheck to use our native healthcheck script for Octane
    healthcheck:
      test: ["CMD", "healthcheck-octane"]
      start_period: 10s

Octane enhancements in the future

Once v4.0 is out, then we can either:

  1. Optimize our /etc/frankenphp/Caddyfile to have better support for Laravel Octane & Classic Mode
  2. Make a /etc/frankenphp/octane.caddyfile that has cherry picked features specifically for Octane, but also has the enhancements of CloudFlare, etc

Octane is very important so this would likely be a v4.1 or v4.2 thing.

Thoughts?

@kohenkatz

Copy link
Copy Markdown
Contributor

@jaydrogers I agree, I think it makes sense to go with what's there now for 4.0 and improve in anther release. (As one of my teachers used to say, "don't let the perfect be the enemy of the good".)

Regarding your two suggested options: theoretically it would be nice to have a single unified base configuration with as few differences as possible (i.e. your first option). However, as @aSeriousDeveloper points out, Octane expects certain variables to do certain things, so it's more likely that a separate file which takes those things into account probably makes more sense (i.e. your second option).

@aSeriousDeveloper

Copy link
Copy Markdown
Contributor Author

I agree with the above.

I think I might include my secret third option of making a PR to Octane to rename their environment variables to something more specific to itself.

That would make it a fair bit easier to integrate with it, but the viability of this depends entirely on the PR actually getting accepted lol.

@jaydrogers
jaydrogers changed the base branch from 280-create-a-frankenphp-variation to main November 19, 2025 17:11
@jaydrogers

Copy link
Copy Markdown
Member

Thanks! I appreciate you both giving me the confidence to launch v4.0.0. Shortly after, we'll have PHP 8.5 support.

In regards to improving Octane support, I am very interested in making sure we're offering a "batteries included" experience with Octane. So please let me know if you have any ideas on this discussion.

I feel as we continue to build and learn with these new technologies, the solution will basically present itself of what we'll need to do. I'll be sure to implement something following the principles of something that "just works" out of the box 😃

Please keep me posted as you learn more. I am beyond grateful to have both of your contributions!

@dbpolito

dbpolito commented Dec 19, 2025

Copy link
Copy Markdown

Having a /etc/frankenphp/octane.caddyfile and maybe having some prefixes on the vars:

SERVERSIDEUP_CADDY...
SERVERSIDEUP_FRANKEN...

So we can load the variables set from Octane, maybe adding some fallback in case it's gone... Because as stated previously, as octane is setting all these variables when creating the process what we set is ignored...

That way we could could have a good SERVERSIDEUP_FRANKENPHP_CONFIG that will just point to /var/www/html/public/frankenphp-worker.php as expected, etc... or come up with more specific envs if needed.

@andreasdorfer

andreasdorfer commented Dec 28, 2025

Copy link
Copy Markdown

Hi everyone,

I am currently working on migrating my application's container from the deprecated Unit server to FrankenPHP with Octane (in worker mode) using the SSU image.
Just wanted to open a new issue when I came across this one, which pretty much covers my findings. I believe I found a configuration pattern that solve the "Octane vs SSU caddyfile" issue.

Instead of starting FrankenPHP via artisan and using CMD ["php", "artisan", "octane:start"...], we should rely on the native FrankenPHP binary and the ServerSideUp Caddyfile, configuring the worker bridge via existing environment variables.

Why avoid octane:start in Docker? While octane:start is great for local dev, using it as the Docker CMD inside this specialized image acts as a "downgrade":

  • Bypassed Optimization: It generates a temporary, basic Caddyfile (from Laravel's stubs), completely ignoring the heavily tuned ServerSideUp Caddyfile (headers, security, compression, logging...).
  • Process Management: It runs PHP Artisan as the process wrapper, rather than letting the container's entrypoint manage the FrankenPHP binary directly.

We can achieve full Worker Mode while keeping all SSU image features by aligning the environment variables like this:

working_dir: /var/www/be
environment:
    APP_BASE_DIR: /var/www/html
    CADDY_SERVER_ROOT: /var/www/html/public
    CADDY_SERVER_EXTRA_DIRECTIVES: "try_files {path} /frankenphp-worker.php"
    CADDY_PHP_SERVER_OPTIONS: "resolve_root_symlink"
    FRANKENPHP_CONFIG: "worker /var/www/html/public/frankenphp-worker.php 4"
    PHP_MEMORY_LIMIT: 512M
    PHP_OPCACHE_ENABLE: 1
    PHP_SESSION_COOKIE_SECURE: 0
    SSL_MODE: "off"

How it works:

  • FRANKENPHP_CONFIG: Starts the actual worker threads in the background using the specified script and thread count.
  • CADDY_SERVER_EXTRA_DIRECTIVES: Modifies the global routing. Caddy attempts to serve the file from disk first (getting all the SSU performance snippet benefits). If the file is missing, it routes the request specifically to Octane's frankenphp-worker.php instead of the default index.php.
  • Result: You get the full speed of Octane workers for PHP, plus the full speed and security of the SSU Caddyfile for everything else.

If the classic mode is desired, skip the CADDY_SERVER_EXTRA_DIRECTIVES and FRANKENPHP_CONFIG variables.
I just didn't try to set up the watch mode so far.

Btw, the docs miss a critical part:

php artisan octane:install

@kohenkatz

kohenkatz commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

@andreasdorfer I think this is a good start, but there are a few downsides related to this point:

  • Process Management: It runs PHP Artisan as the process wrapper, rather than letting the container's entrypoint manage the FrankenPHP binary directly.

There are several things that artisan octane:start does to help set up the application, that are not included in your example:

  1. Set up Mercure config based on the Laravel config
  2. Set up max_execution_time based on Laravel config
  3. Sets an environment variable LARAVEL_OCTANE, which is used by the framework to help clean up between requests and handle streaming responses (though it's unclear if that's needed for FrankenPHP, or only for Swoole)
  4. Allow using FrankenPHP's "watch" mode
  5. Creation of the frankenphp-worker.php file, if it is not committed in the project
  6. Set a count of max requests before restart to mitigate memory leaks
  7. Allow restarting FrankenPHP with artisan octane:reload (using Caddy's admin API)

Not all of those things are deal-breakers, but they are certainly important to be aware of for designing a solution. I think the first three are the most important, and the last is not worth worrying about (since you aren't actually running "octane") but should be documented.

@aSeriousDeveloper

Copy link
Copy Markdown
Contributor Author

Seems someone has made a change in Octane that would benefit here: laravel/octane#1095

This seems to allow you to inject additional environment variables to use in an Octane Caddyfile, so that you can use a custom / modified Caddyfile while still calling artisan octane.

@jaydrogers

Copy link
Copy Markdown
Member

This looks promising! But we still probably want to use our Caddyfile since it has a number of optimizations compared to Octane (ie. performance caching, security headers, etc)?

This hasn't fallen off my radar. I'm starting to roll my first FrankenPHP apps into production now and I am hoping to gain more experience with Octane before I'm able to have a valid opinion 🤓

I'd love to see continued discussion on this so I can learn from others too 👍

@andreasdorfer

Copy link
Copy Markdown

@kohenkatz I agree - my approach is not “plug-and-play” and you need to understand what you're doing.
That said, I’ve been running this setup in production for about a month now without issues. Most of the points you listed are either optional or can be handled explicitly outside artisan octane:start.

For others who may find this issue later, here’s how I’d address each point:

1. Mercure config from Laravel config
Not relevant in my case. I don’t use Mercure; I use Reverb. For Mercure users, this can also be configured explicitly via env vars.

2. max_execution_time from Laravel config
I handle this directly via env variables (e.g. REQUEST_MAX_EXECUTION_TIME) at container/runtime level, instead of relying on Octane command wiring.

3. LARAVEL_OCTANE env variable
Agreed this one matters. I set LARAVEL_OCTANE=1 explicitly in the Docker image/runtime env.

4. FrankenPHP watch mode
Also supported directly via FRANKENPHP_CONFIG, e.g.:

   FRANKENPHP_CONFIG: |
     worker {
       file /var/www/be/public/frankenphp-worker.php
       num 4
       watch /var/www
     }

5. Auto-create frankenphp-worker.php
If needed, run artisan octane:install once (or commit the worker file to the repo).

6. Max requests before worker restart
Already handled in frankenphp-worker.php (default 1000), and easy to override via MAX_REQUESTS.

7. octane:reload / Caddy admin API integration
I use a containerized deployment, process lifecycle is managed by Docker - not required for my setup.

Anyway, I’ll give it a try and see whether the latest updates to StartFrankenPhpCommand.php can simplify my setup.
But, I still doubt that the artisan octane:start wrapper is required in a Docker-native production setup atm.

@Abdulmajeed-Jamaan

Abdulmajeed-Jamaan commented Aug 19, 2026

Copy link
Copy Markdown

I would first analyze the differences between octane caddyfile and serversideup caddy file, and see are the changes only additive or it require replacement

Since serversideup is more bigger and have many optimizations, i would start with aiming to see whats the least stuff from octane caddyfile needed to run octane ?

then can do either of the following:

  • PR to the octane package to make everything oveeridable with env variable Except the most necessary to run octane properly
  • New laravel package with necessary logic with a command called php artisan octane:serversideup
  • New docker image flavor called "frankenphp-octane-v2.19" that is versioned aligning with laravel octane and have a new caddy file specified for it created from serversideup

@jaydrogers
jaydrogers changed the base branch from main to release/webserver-improvements-and-fixes September 11, 2026 18:02
jaydrogers and others added 4 commits September 11, 2026 13:02
- Updated environment variable documentation to clarify the behavior of CADDY_ADMIN, CADDY_GLOBAL_OPTIONS, CADDY_LOG_FORMAT, and CADDY_LOG_OUTPUT when using Laravel Octane.
- Added a new health check command for Octane to verify server status.
- Refactored the test image script to handle multiple containers and improve health check logic.
- Modified SSL generation script to account for FrankenPHP's unique requirements.
- Adjusted Dockerfile to set default logging format and output for better compatibility with Octane.
- Enhanced Caddyfile configuration to support Octane's worker and global options.
- Introduced new log format files to handle authorization redaction in logs.
- Created README files in configuration directories to guide users on custom Caddyfile usage.
- Implemented separate Caddyfile configurations for Octane to manage worker and global settings effectively.
@jaydrogers jaydrogers linked an issue Sep 16, 2026 that may be closed by this pull request
@jaydrogers

Copy link
Copy Markdown
Member

I want to thank everyone who participated on this thread! I want to give a huge thanks to everyone. I am preparing a 5.0 release and figured now was the time to get Octane support in our Caddyfile.

I pushed some updates on this branch that took a different approach, but I think this approach will work well. I wanted to keep it on this branch to give you all the credit.

Feel free to take a look at what I have and scrutinize it. I spent a few days on this, but overall I feel good about it. I like that it "just works" and gives us the optimizations that we need.


Note

Summary of my changes summarized by Fable 5.1 🤖

Summary

Laravel Octane can now run with the Caddyfile that ships in the FrankenPHP image. Add --caddyfile=/etc/frankenphp/Caddyfile to octane:start and the image switches into worker mode while keeping everything classic mode already has: trusted proxies, security headers, static asset caching, SSL modes, the /healthcheck endpoint, the /storage PHP block, and the CADDY_* variables.

command: ["php", "artisan", "octane:start", "--server=frankenphp", "--port=8080", "--caddyfile=/etc/frankenphp/Caddyfile"]

How it works

Octane sets LARAVEL_OCTANE=1 in FrankenPHP's environment every time it starts the server. The Caddyfile uses that as a mode switch, the same way it already handles SSL_MODE, TRUSTED_PROXY, and CADDY_AUTO_HTTPS:

frankenphp {
	{$FRANKENPHP_CONFIG}
	import laravel-octane/frankenphp/{$LARAVEL_OCTANE:0}.caddyfile
}

Three small files under laravel-octane/ do the switching:

  • global/1.caddyfile turns on the Caddy admin API on the host and port Octane passes, which octane:status, octane:reload, and octane:stop need.
  • frankenphp/1.caddyfile defines the worker from CADDY_SERVER_ROOT/frankenphp-worker.php using Octane's --workers and --watch values.
  • php-server/1.caddyfile routes every request to the worker instead of index.php.

The matching 0.caddyfile files are comment-only, so classic mode is unchanged. There is no wrapper script, no second Caddyfile to keep in sync, and nothing new for users to set.

Why not the approach in #604: it placed num and watch outside worker { }, which FrankenPHP rejects, and it still needed a hand-written worker block in FRANKENPHP_CONFIG and CADDY_PHP_SERVER_OPTIONS. Once Octane passes its own --workers and --watch, that workaround collides with itself.

Breaking changes

Two FrankenPHP defaults change. Both follow what Caddy and the official FrankenPHP image do, and both are required for Octane, which only relays stderr and only parses JSON.

Variable v4 v5
CADDY_LOG_OUTPUT stdout stderr
CADDY_LOG_FORMAT console auto (Caddy's default: console on a terminal, json otherwise)

You are affected if you read FrankenPHP's stdout and stderr separately, or if something parses the console lines. docker logs, Compose, and Kubernetes capture both streams, so most setups only notice the format. The v4 values are still available with CADDY_LOG_FORMAT=console and CADDY_LOG_OUTPUT=stdout.

One more: the old workaround of a worker { } block in FRANKENPHP_CONFIG combined with --caddyfile now fails with "global workers must not have duplicate filenames". Remove the block. The migration guide covers it.

Why now: v5 is already a major release because of the OPcache changes, so this is the release to fix defaults. The v4 values were our own choice, not upstream's. Forcing console puts ANSI color codes into every log pipeline that is not a terminal, and stdout makes Octane drop the logs entirely. Fixing both in the same major means one upgrade for users instead of two.

Also in this PR

  • Access logs redact the authorization query parameter that Mercure subscribers pass in the URL, in every format. This is the filter from FrankenPHP's own Caddyfile. Octane's stub tries the same but filters uri instead of request>uri, so upstream it never matches.
  • 5-generate-ssl.sh generates the key pair whenever SSL_MODE is not off in the FrankenPHP image, so SSL_MODE=full works when Octane is the container command.
  • caddyfile.d/ and caddyfile-global.d/ ship a README so Caddy stops logging "No files matching import glob pattern" on every start.
  • Docs: a rewritten Octane guide (health checks, logging, and how every octane:start option behaves with our Caddyfile), a logging section explaining which stream each variation uses and why, and the v5 migration guide.

Testing

scripts/test-image.sh, which CI runs against every built image, now starts a second FrankenPHP container with the variables Octane sets and checks worker routing, the /storage PHP block, the admin API on Octane's port, JSON on stderr, and the authorization redaction.

Verified end-to-end with a real Laravel 13 + Octane 2.19.1 app: octane:start --caddyfile, octane:status, octane:reload, SSL_MODE=full, and a graceful docker stop with requests in flight.

@jaydrogers
jaydrogers merged commit 5379ee6 into serversideup:release/webserver-improvements-and-fixes Sep 16, 2026
79 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrankenPHP with Octane uses default FrankenPHP Caddyfile

6 participants